import javax.swing.*;
import java.util.Arrays;

/**
 * Lambda表达式是Java 8的重要更新，
 * Lambda表达式支持将代码块作为方法参数，
 * Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口（这种接口被称为函数式接口）的实例。
 * <p>
 * 下面先使用匿名内部类来改写前面介绍的command表达式的例子，改写后的程序如下。
 */
interface Command {
    // 接口里定义的process方法用于封装“处理行为”
    void process(int element);
}

class ProcessArray {
    public void process(int[] target, Command cmd) {
        for (int t : target) {
            cmd.process(t);
        }
    }
}

class CommandTest {
    public static void test() {
        ProcessArray pa = new ProcessArray();
        int[] target = {3, -4, 6, 4};
        pa.process(target, new Command() {
            @Override
            public void process(int element) {
                System.out.println("数组元素的平方是:" + element * element);
            }
        });
    }
}

/**
 * Lambda表达式完全可用于简化创建匿名内部类对象，因此可将上面代码改为如下形式。
 */
class CommandTest2 {
    public static void test() {
        ProcessArray pa = new ProcessArray();
        int[] target = {3, -4, 6, 4};
        pa.process(target, (int element) -> {
            System.out.println("数组元素的平方是:" + element * element);
        });
    }
}

/**
 * 从上面介绍可以看出，当使用Lambda表达式代替匿名内部类创建对象时，
 * Lambda表达式的代码块将会代替实现抽象方法的方法体，Lambda表达式就相当一个匿名方法。
 * <p>
 * 从上面语法格式可以看出，Lambda表达式的主要作用就是代替匿名内部类的烦琐语法。它由三部分组成。
 * ➢ 形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数，甚至连形参列表的圆括号也可以省略。
 * ➢ 箭头（->）。必须通过英文中画线和大于符号组成。
 * ➢ 代码块。如果代码块只包含一条语句，Lambda表达式允许省略代码块的花括号，那么这条语句就不要用花括号表示语句结束。
 * Lambda代码块只有一条return语句，甚至可以省略return关键字。
 * Lambda表达式需要返回值，而它的代码块中仅有一条省略了return的语句，Lambda表达式会自动返回这条语句的值。
 * <p>
 * 下面程序示范了Lambda表达式的几种简化写法。
 */
interface Eatable {
    void taste();
}

interface Flyable {
    void fly(String weather);
}

interface Addable {
    int add(int a, int b);
}

class LambdaQs {
    // 调用该方法需要Eatable对象
    public void eat(Eatable e) {
        System.out.println(e);
        e.taste();
    }

    // 调用该方法需要Flyable对象
    public void drive(Flyable f) {
        System.out.println("我正在驾驶：" + f);
        f.fly("【碧空如洗的晴日】");
    }

    // 调用该方法需要Addable对象
    public void plus(Addable add) {
        System.out.println("5与3的和为：" + add.add(5, 3));
    }

    public static void test() {
        LambdaQs lq = new LambdaQs();
        // Lambda表达式的代码只有一条语句，可以省略花括号
        lq.eat(() -> System.out.println("苹果的味道不错！"));
        // Lambda表达式的形参列表只有一个形参，可以省略圆括号
        lq.drive(weather -> {
            System.out.println("今天天气是：" + weather);
            System.out.println("直升机飞行平稳");
        });
        // Lambda表达式的代码块只有一条语句，可以省略花括号
        // 代码块中只有一条语句，即使该表达式需要返回值，也可以省略return关键字
        lq.plus((a, b) -> a + b);

    }
}

/**
 * Lambda表达式与函数式接口
 * <p>
 * Lambda表达式的类型，也被称为“目标类型（target type）”，
 * Lambda表达式的目标类型必须是“函数式接口（functional interface）”。
 * 函数式接口代表只包含一个抽象方法的接口。
 * 函数式接口可以包含多个默认方法、类方法，但只能声明一个抽象方法。
 * <p>
 * 如果采用匿名内部类语法来创建函数式接口的实例，则只需要实现一个抽象方法，
 * 在这种情况下即可采用Lambda表达式来创建对象，该表达式创建出来的对象的目标类型就是这个函数式接口。
 * 查询Java 8的API文档，可以发现大量的函数式接口，例如：Runnable、ActionListener等接口都是函数式接口。
 * <p>
 * 提示：Java 8专门为函数式接口提供了@FunctionalInterface注解，该注解通常放在接口定义前面，
 * 该注解对程序功能没有任何作用，它用于告诉编译器执行更严格检查—检查该接口必须是函数式接口，否则编译器就会报错。
 */
class LambdaTest1 {
    public static void test() {
        // Runnable接口中只包含一个无参数的方法
        // Lambda表达式代表的匿名方法实现了Runnable接口中唯一的、无参数的方法
        // 因此下面的Lambda表达式创建了一个Runnable对象
        Runnable r = () -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(i);
            }
        };
        r.run();
    }
}

/**
 * Lambda表达式实现的是匿名方法—因此它只能实现特定函数式接口中的唯一方法。
 * 这意味着Lambda表达式有如下两个限制。
 * ➢ Lambda表达式的目标类型必须是明确的函数式接口。
 * ➢ Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法，因此它只能为只有一个抽象方法的接口（函数式接口）创建对象。
 */
class LambdaTest2 {
    public static void test() {
        // 关于上面第一点限制，下面将会编译错误： 不兼容的类型：Object不是函数接口
//        Object obj = () -> {
//            for (int i = 0; i < 3; i++) {
//                System.out.println(i);
//            }
//        };
    }
}

/**
 * 为了保证Lambda表达式的目标类型是一个明确的函数式接口，可以有如下三种常见方式。
 * ➢ 将Lambda表达式赋值给函数式接口类型的变量。
 * ➢ 将Lambda表达式作为函数式接口类型的参数传给某个方法。
 * ➢ 使用函数式接口对Lambda表达式进行强制类型转换。
 */
class LambdaTest3 {
    public static void test() {
        // 因此，只要将上面代码改为如下形式即可
        Object obj = (Runnable) () -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(i);
            }
        };
    }
}

/**
 * 需要说明的是，同样的Lambda表达式的目标类型完全可能是变化的—唯一的要求是，
 * Lambda表达式实现的匿名方法与目标类型（函数式接口）中唯一的抽象方法有相同的形参列表。
 * 例如定义了如下接口
 */
@FunctionalInterface
interface FKTest {
    void run();
}

/**
 * 上面的函数式接口中仅定义了一个不带参数的方法，
 * 因此前面强制转型为Runnable的Lambda表达式也可强转为FkTest类型—因为FkTest接口中的唯一的抽象方法是不带参数的，
 * 而该Lambda表达式也是不带参数的。
 * 因此，下面代码是正确的
 */
class LambdaTest4 {
    public static void test() {
        // 因此，只要将上面代码改为如下形式即可
        Object obj = (FKTest) () -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(i);
            }
        };
    }
}

/**
 * 方法引用与构造器引用
 * 如果Lambda表达式的代码块只有一条代码，程序就可以省略Lambda表达式中代码块的花括号。
 * 不仅如此，如果Lambda表达式的代码块只有一条代码，还可以在代码块中使用方法引用和构造器引用。
 * 方法引用和构造器引用可以让Lambda表达式的代码块更加简洁。
 * 方法引用和构造器引用都需要使用两个英文冒号。
 */
// 该函数式接口中包含一个convert()抽象方法，该方法负责将String参数转换为Integer。
@FunctionalInterface
interface Converter {
    Integer convert(String from);
}

@FunctionalInterface
interface MyTest {
    String test(String a, int b, int c);
}

@FunctionalInterface
interface YourTest {
    JFrame win(String title);
}

class FunctionRefence {
    public static void test() {
        // 1.引用类方法
        // 下面代码使用Lambda表达式来创建一个Converter对象
        Converter converter1 = from -> Integer.valueOf(from);
        Integer val = converter1.convert("99");
        System.out.println(val);
        // 上面Lambda表达式的代码块只有一行调用类方法的代码，因此可以使用如下方法引用进行替换
        // 函数式接口中被实现方法的全部参数传递给类方法作为参数
        Converter converter2 = Integer::valueOf;
        Integer val2 = converter2.convert("99");
        System.out.println(val2);

        // 2.引用特定对象的实例方法
        // 先使用Lambda表达式来创建一个Converter对象
        Converter converter3 = from -> "fkit.org".indexOf(from);
        Integer value = converter3.convert("it");
        System.out.println(value);
        // 上面Lambda表达式的代码块只有一行调用"fkit.org"的indexOf()实例方法的代码，因此可以使用如下方法引用进行替换
        // 函数式接口中被实现方法的全部参数传给该方法作为参数
        Converter converter4 = "fkit.org"::indexOf;
        Integer value2 = converter4.convert("it");
        System.out.println(value2);

        // 3.引用某类对象的实例方法
        // 使用Lambda表达式创建MyTest对象
        MyTest mt = (a, b, c) -> a.substring(b, c);
        String str = mt.test("Java I Love you", 2, 9);
        System.out.println(str);
        // 上面Lambda表达式的代码块只有一行a.substring(b, c);，因此可以使用如下方法引用进行替换
        // 函数式接口中被实现方法的第一个参数作为调用者
        // 后面的参数全部传给该方法作为参数
        MyTest mt2 = String::substring;
        String str2 = mt2.test("Java I Love you", 2, 9);
        System.out.println(str2);

        // 4.引用构造器
        // 下面代码使用Lambda表达式来创建一个YourTest对象
        YourTest yt = a -> new JFrame(a);
        JFrame jf = yt.win("我的窗口");
        System.out.println(jf);
        // 上面Lambda表达式的代码块只有一行new JFrame(a);，因此可以使用如下构造器引用进行替换
        YourTest yt2 = JFrame::new;
        JFrame jf2 = yt2.win("我的窗口2");
        System.out.println(jf2);
        /*
         * 对于上面的构造器引用，也就是调用某个JFrame类的构造器来实现YourTest函数式接口中唯一的抽象方法，
         * 当调用YourTest接口中的唯一的抽象方法时，调用参数将会传给JFrame构造器。
         * 从上面程序中可以看出，调用YourTest对象的win()抽象方法时，实际只传入了一个String类型的参数，
         * 这个String类型的参数会被传给JFrame构造器—这就确定了是调用JFrame类的、带一个String参数的构造器。
         */
    }
}

/**
 * Lambda表达式与匿名内部类的联系和区别
 * 从前面介绍可以看出，Lambda表达式是匿名内部类的一种简化，因此它可以部分取代匿名内部类的作用，
 * Lambda表达式与匿名内部类存在如下相同点。
 * ➢ Lambda表达式与匿名内部类一样，都可以直接访问“effectively final”的局部变量，以及外部类的成员变量（包括实例变量和类变量）。
 * ➢ Lambda表达式创建的对象与匿名内部类生成的对象一样，都可以直接调用从接口中继承的默认方法。
 * <p>
 * Lambda表达式与匿名内部类主要存在如下区别。
 * ➢ 匿名内部类可以为任意接口创建实例—不管接口包含多少个抽象方法，只要匿名内部类实现所有的抽象方法即可；但Lambda表达式只能为函数式接口创建实例。
 * ➢ 匿名内部类可以为抽象类甚至普通类创建实例；但Lambda表达式只能为函数式接口创建实例。
 * ➢ 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法；但Lambda表达式的代码块不允许调用接口中定义的默认方法。
 * <p>
 * 下面程序示范了Lambda表达式与匿名内部类的相似之处与不同之处
 */
@FunctionalInterface
interface Displayable {
    void display();

    default int add(int a, int b) {
        return a + b;
    }
}

class LambdaAndInner {
    private int age = 12;
    private static String name = "Java爱好者";

    public void _test() {
        String book = "Java从入门到精通";
        Displayable dis = () -> {
            // 访问“effectively final”的局部变量
            System.out.println("book局部变量为：" + book);
            // 访问外部类的实例变量和类变量
            System.out.println("外部类的age实例变量为：" + age);
            System.out.println("外部类的name类变量为：" + name);

            // Lambda表达式不允许调用接口中的默认方法
//            System.out.println(add(3, 5));
        };
        dis.display();
        System.out.println(dis.add(3, 5));

        // 匿名内部类实现抽象方法时，可以在其中调用接口的默认方法
        Displayable dis2 = new Displayable() {
            @Override
            public void display() {
                System.out.println(add(3, 5));
            }
        };
        dis2.display();
    }

    public static void test() {
        new LambdaAndInner()._test();
    }
}

/**
 * 使用Lambda表达式调用Arrays的类方法
 * 前面介绍Array类的功能时已经提到，Arrays类的有些方法需要Comparator、XxxOperator、XxxFunction等接口的实例，
 * 这些接口都是函数式接口，因此可以使用Lambda表达式来调用Arrays的方法。
 * 例如如下程序。
 */
class LambdaArrays {
    public static void test() {
        String[] arr1 = new String[]{"Java", "fkava", "fkit", "ios", "android"};
        Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
        System.out.println(Arrays.toString(arr1));

        int[] arr2 = new int[]{3, -4, 25, 16, 30, 18};
        // left代表数组中前一个索引处的元素，计算第一个元素时，left为1
        // right代表数组中当前索引处的元素
        Arrays.parallelPrefix(arr2, (left, right) -> left * right);
        System.out.println(Arrays.toString(arr2));

        long[] arr3 = new long[5];
        Arrays.parallelSetAll(arr3, operand -> operand * 5);
        System.out.println(Arrays.toString(arr3));
    }
}

public class LambdaTest {
    public static void main(String[] args) {
        CommandTest.test();
        CommandTest2.test();
        LambdaQs.test();
        LambdaTest1.test();
        LambdaTest2.test();
        LambdaTest3.test();
        LambdaTest4.test();
        FunctionRefence.test();
        LambdaAndInner.test();
        LambdaArrays.test();
    }
}
